#ifndef __MATRIX_H__
#define __MATRIX_H__

#include<string>
#include<iostream>
#include<stdexcept>
#include<cmath>

#define __MATRIX_EACHROW(__i) for(unsigned int __i=0 ; __i < _rows ; __i++ )
#define __MATRIX_EACHCOL(__j) for(unsigned int __j=0 ; __j < _cols ; __j++ )
#define __MATRIX_EACH(__i,__j) __MATRIX_EACHROW(__i) __MATRIX_EACHCOL(__j)

#define __MATRIX_EACHROW_M(__m,__i) for(unsigned int __i=0 ; __i < __m._rows ; __i++ )
#define __MATRIX_EACHCOL_M(__m,__j) for(unsigned int __j=0 ; __j < __m._cols ; __j++ )
#define __MATRIX_EACH_M(__m,__i,__j) __MATRIX_EACHROW_M(__m,__i) __MATRIX_EACHCOL_M(__m,__j)

class Matrix
{

public:
	Matrix(unsigned int rows, unsigned int cols, const double* arr);
	Matrix(unsigned int rows, unsigned int cols, const double** arr);
	Matrix(unsigned int rows, unsigned int cols);
	Matrix(const Matrix& m);
	virtual ~Matrix();
	
	// cantidad de filas
	unsigned int rows() const;

	// cantidad de columnas
	unsigned int cols() const;

	// devuelve true si la matriz es cuadrada
	bool isSquare() const;

	// devuelve la traspuesta la matriz
	Matrix transposed() const;

	// devuelve el menor ij de la matriz
	Matrix minor(unsigned int i, unsigned int j) const;

	// devuelve el vector columna j
	Matrix col(unsigned int j) const;

	// devuelve el vector fila i
	Matrix row(unsigned int i) const;

	// operaciones básicas
	virtual Matrix operator* ( const Matrix& b ) const;
	virtual Matrix operator* ( const double  k ) const;
	virtual Matrix operator+ ( const Matrix& b ) const;
	virtual Matrix operator- ( const Matrix& b ) const;

	virtual double& operator()( unsigned int i, unsigned int j );
	virtual const double operator()( unsigned int i, unsigned int j ) const;

	// operadores que modifican la instancia
	virtual Matrix& operator=  ( const Matrix& b );
	virtual Matrix& operator*= ( const Matrix& b );
	virtual Matrix& operator*= ( const double  k );
	virtual Matrix& operator/= ( const double  k );
	virtual Matrix& operator+= ( const Matrix& b );
	virtual Matrix& operator-= ( const Matrix& b );

	virtual double FrobeniusNorm() const;

	// comparación
	bool operator== ( const Matrix& b ) const;

	// representa a la matriz en un ostream
	std::ostream& print( std::ostream& out ) const;

	// carga una matriz de un istream
	std::istream& read( std::istream& in );

	// intercambia la fila i1 con la i2
	void swapRows( unsigned int i1, unsigned int i2 );
	// intercambia la columna j1 con la j2
	void swapCols( unsigned int j1, unsigned int j2 );
	// hace la operación F_i1 - F_i2*k
	void substractRows( unsigned int i1, unsigned int i2, double k=1 );
	
protected:

	unsigned int _rows, _cols;
	double** _matrix;

	// busca el máximo en valor absoluto de la submatriz a partir de i0,j0
	double absmax( unsigned int& i0, unsigned int& j0 ) const;

private:
	void createMatrix();

};

Matrix operator* ( const double k, const Matrix& m );
std::ostream& operator<< ( std::ostream& out, const Matrix& m );
std::istream& operator>> ( std::istream& in, Matrix& m );

#endif

